package com.jdragon.common.proxy;

import com.jdragon.common.utils.FreeMakerUtil;

import javax.tools.*;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: Jdragon
 * @email: 1061917196@qq.com
 * @Date: 2020.12.09 22:04
 * @Description:
 */
public class JdkProxyFactory {

    private final static String LN = System.lineSeparator();

    private final static AtomicInteger PROXY_INDEX = new AtomicInteger(0);

    private final static Boolean SAVE_GENERATED_FILES = Boolean.valueOf(System.getProperty("sun.misc.ProxyGenerator.saveGeneratedFiles"));

    private final static String USER_DIR = System.getProperty("user.dir") + "/com/jdragon/proxy/";

    private final static String PACKAGE_NAME = "com.jdragon.proxy";

    public static Object newProxyInstance(ClassLoader classLoader,
                                          @NotNull Class<?>[] interfaces,
                                          @NotNull InvocationHandler h) {
        try {
            if (interfaces.length == 0) {
                throw new Exception("至少要实现一个接口");
            }
            //使用被代理类的类名和自增数定义代理类的名字
            String proxyClass = interfaces[0].getSimpleName() + "$Proxy" + PROXY_INDEX.incrementAndGet();
            //加载代理类
            Class<?> loadClass = loadClass(interfaces[0], proxyClass);
            Constructor<?> constructor = loadClass.getDeclaredConstructor(InvocationHandler.class);
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @Description: 加载类
    **/
    private static Class<?> loadClass(Class<?> interfaces, String proxyClassName) throws Exception {
        String classPath = PACKAGE_NAME + "." + proxyClassName;
        //构建源代码
        String sourceCode = generateSourceCode(interfaces, proxyClassName);
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null))) {
            List<JavaFileObject> files = Collections.singletonList(new MemoryJavaFileObject(proxyClassName, sourceCode));
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, files);
            if (!task.call()) {
                throw new Exception("任务调用异常");
            }
            ClassLoader classLoader = manager.getClassLoader(null);
            Class<?> aClass = manager.getClassLoader(null).loadClass(classPath);
            if (SAVE_GENERATED_FILES) {
                save(proxyClassName, classLoader);
            }
            return aClass;
        }
    }

    /**
     * @Description: 构造源代码
    **/
    private static String generateSourceCode(Class<?> interfaces, String proxyClassName) {
        String interfaceName = interfaces.getName();
        List<MethodEntity> methodEntities = new ArrayList<>();
        methodEntities.add(new MethodEntity(Object.class, "toString", null,
                String.class));
        methodEntities.add(new MethodEntity(Object.class, "hashCode", null,
                int.class));
        methodEntities.add(new MethodEntity(Object.class, "equals", Collections.singletonList(Object.class.getName()),
                boolean.class));

        for (Method declaredMethod : interfaces.getDeclaredMethods()) {
            MethodEntity methodEntity = new MethodEntity();
            methodEntity.setClassName(interfaces);
            methodEntity.setMethodName(declaredMethod.getName());
            List<String> params = new ArrayList<>();
            for (Parameter parameter : declaredMethod.getParameters()) {
                String paramTypeName = parameter.getType().getName();
                params.add(paramTypeName);
            }
            methodEntity.setParamList(params);
            methodEntity.setRetType(declaredMethod.getReturnType());
            methodEntity.setTransferType(declaredMethod.getReturnType());
            methodEntities.add(methodEntity);
        }

        //利用定义好的模板传入参数到freemaker进行遍历填充，最后获得源代码
        Map<String, Object> map = new HashMap<>(8);
        map.put("package", PACKAGE_NAME);
        map.put("className", proxyClassName);
        map.put("interface", interfaceName);
        map.put("methodList", methodEntities);
        FreeMakerUtil freeMakerUtil = new FreeMakerUtil("/template/freemaker/", "ftl");
        return freeMakerUtil.printString("proxy", map);
    }

    private static void save(String proxyClassName, ClassLoader classLoader) throws IOException {
        byte[] classByte = ((MemoryClassLoader) classLoader).getClassByte(PACKAGE_NAME + "." + proxyClassName);
        File f = new File(USER_DIR, proxyClassName + ".class");
        if (!f.getParentFile().exists()) {
            if (!f.getParentFile().mkdirs()) {
                throw new IOException("创建保存目录失败");
            }
        }
        FileOutputStream fw = new FileOutputStream(f);
        fw.write(classByte);
        fw.close();
    }
}
